'use client';

import React, { useState, useEffect } from 'react';
import { id, lookup, InstaQLEntity } from '@instantdb/react';

import db from '../lib/db';
import schema from '../instant.schema';

// Instant utility types for query results
type PostsWithProfile = InstaQLEntity<
  typeof schema,
  'posts',
  { author: { avatar: {} } }
>;

function randomHandle() {
  const adjectives = ['Quick', 'Lazy', 'Happy', 'Sad', 'Bright', 'Dark'];
  const nouns = ['Fox', 'Dog', 'Cat', 'Bird', 'Fish', 'Mouse'];
  const randomAdjective =
    adjectives[Math.floor(Math.random() * adjectives.length)];
  const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
  const randomSuffix = Math.floor(Math.random() * 9000) + 1000;
  return `${randomAdjective}${randomNoun}${randomSuffix}`;
}

// Write Data
// ---------
async function createProfile(userId: string) {
  // CRITICAL: transact is how you write data to the database
  // We want to block until the profile is created, so we use await
  await db.transact(
    db.tx.profiles[userId]
      .update({
        handle: randomHandle(),
      })
      .link({ user: userId }),
  );
}

function addPost(text: string, authorId: string | undefined) {
  db.transact(
    // CRITICAL: ids must be a valid UUID, so we use `id()` to generate one
    db.tx.posts[id()]
      .update({
        text,
        createdAt: Date.now(),
      })
      .link({ author: authorId }),
  );
}

function deletePost(postId: string) {
  db.transact(db.tx.posts[postId].delete());
}

// Ephemeral helpers
// ---------
function makeShout(text: string) {
  const maxX = window.innerWidth - 200; // Leave some margin
  const maxY = window.innerHeight - 100;
  return {
    text,
    x: Math.random() * maxX,
    y: Math.random() * maxY,
    angle: (Math.random() - 0.5) * 30,
    size: Math.random() * 20 + 18,
  };
}

function addShout({
  text,
  x,
  y,
  angle,
  size,
}: {
  text: string;
  x: number;
  y: number;
  angle: number;
  size: number;
}) {
  const shoutElement = document.createElement('div');
  shoutElement.textContent = text;
  shoutElement.style.cssText = `
    left: ${x}px;
    top: ${y}px;
    position: fixed;
    z-index: 9999;
    font-size: ${size}px;
    font-weight: bold;
    pointer-events: none;
    transition: opacity 2s ease-out;
    opacity: 1;
    font-family: system-ui, -apple-system, sans-serif;
    white-space: nowrap;
    transform: rotate(${angle}deg);
  `;
  document.body.appendChild(shoutElement);
  setTimeout(() => {
    shoutElement.style.opacity = '0';
  }, 100);
  setTimeout(() => {
    shoutElement.remove();
  }, 2100);
}

// Instant query Hooks
// ---------
function useProfile() {
  // CRITICAL: useUser can only be used inside a db.SignedIn component
  const user = db.useUser();
  const { data, isLoading, error } = db.useQuery({
    profiles: {
      $: { where: { 'user.id': user.id } },
      avatar: {},
    },
  });
  const profile = data?.profiles?.[0];

  return { profile, isLoading, error };
}

function useRequiredProfile() {
  const { profile } = useProfile();
  if (!profile) {
    throw new Error('useRequiredProfile must be used inside EnsureProfile');
  }

  return profile;
}

function usePosts(pageNumber: number, pageSize: number) {
  const { isLoading, error, data } = db.useQuery({
    posts: {
      $: {
        order: { createdAt: 'desc' },
        limit: pageSize,
        offset: (pageNumber - 1) * pageSize,
      },
      author: {
        avatar: {},
      },
    },
  });

  return { isLoading, error, posts: data?.posts || [] };
}

// Auth Components
// ---------
function Login() {
  const [sentEmail, setSentEmail] = useState('');

  return (
    <div className="flex min-h-screen items-center justify-center">
      <div className="max-w-sm">
        {!sentEmail ? (
          <EmailStep onSendEmail={setSentEmail} />
        ) : (
          <CodeStep sentEmail={sentEmail} />
        )}
      </div>
    </div>
  );
}

function EmailStep({ onSendEmail }: { onSendEmail: (email: string) => void }) {
  const inputRef = React.useRef<HTMLInputElement>(null);
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const inputEl = inputRef.current!;
    const email = inputEl.value;
    onSendEmail(email);
    db.auth.sendMagicCode({ email }).catch((err) => {
      alert('Uh oh :' + err.body?.message);
      onSendEmail('');
    });
  };
  return (
    <form
      key="email"
      onSubmit={handleSubmit}
      className="flex flex-col space-y-4"
    >
      <h2 className="text-xl font-bold">Instant Demo app</h2>
      <p className="text-gray-700">
        This is a demo app for InstantDB with the following features:
      </p>
      <p className="text-gray-700">
        To try the app, enter your email, and we'll send you a verification
        code. We'll create an account for you too if you don't already have one.
      </p>
      <input
        ref={inputRef}
        type="email"
        className="w-full border border-gray-300 px-3 py-1"
        placeholder="Enter your email"
        required
        autoFocus
      />
      <button
        type="submit"
        className="w-full bg-blue-600 px-3 py-1 font-bold text-white hover:bg-blue-700"
      >
        Send Code
      </button>
    </form>
  );
}

function CodeStep({ sentEmail }: { sentEmail: string }) {
  const inputRef = React.useRef<HTMLInputElement>(null);
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const inputEl = inputRef.current!;
    const code = inputEl.value;
    db.auth.signInWithMagicCode({ email: sentEmail, code }).catch((err) => {
      inputEl.value = '';
      alert('Uh oh :' + err.body?.message);
    });
  };

  return (
    <form
      key="code"
      onSubmit={handleSubmit}
      className="flex flex-col space-y-4"
    >
      <h2 className="text-xl font-bold">Enter your code</h2>
      <p className="text-gray-700">
        We sent an email to <strong>{sentEmail}</strong>. Check your email, and
        paste the code you see.
      </p>
      <input
        ref={inputRef}
        type="text"
        className="w-full border border-gray-300 px-3 py-1"
        placeholder="123456..."
        required
        autoFocus
      />
      <button
        type="submit"
        className="w-full bg-blue-600 px-3 py-1 font-bold text-white hover:bg-blue-700"
      >
        Verify Code
      </button>
    </form>
  );
}

function EnsureProfile({ children }: { children: React.ReactNode }) {
  const user = db.useUser();

  const { isLoading, profile, error } = useProfile();

  useEffect(() => {
    if (!isLoading && !profile) {
      createProfile(user.id);
    }
  }, [isLoading, profile, user.id]);

  if (isLoading) return null;
  if (error)
    return (
      <div className="p-4 text-red-500">Profile error: {error.message}</div>
    );
  if (!profile) return null; // Still creating profile...

  return <>{children}</>;
}

// Use the room for presence and topics
const room = db.room('todos', 'main');

// App Components
// ---------
function Main() {
  const [pageNumber, setPageNumber] = useState(1);
  const pageSize = 5;
  const { isLoading, error, posts } = usePosts(pageNumber, pageSize);

  const { peers } = db.rooms.usePresence(room);
  const numUsers = 1 + Object.keys(peers).length;

  db.rooms.useTopicEffect(room, 'shout', (message) => {
    addShout(message);
  });

  if (isLoading) {
    return;
  }
  if (error) {
    return <div className="p-4 text-red-500">Error: {error.message}</div>;
  }

  const loadNextPage = () => {
    setPageNumber(pageNumber + 1);
  };

  const loadPreviousPage = () => {
    setPageNumber(pageNumber - 1);
  };

  return (
    <div className="min-h-screen p-4">
      <div className="mx-auto max-w-4xl rounded-lg bg-white p-6">
        <div className="mb-6 flex items-start justify-between">
          <ProfileAvatar />
          <button
            onClick={() => db.auth.signOut()}
            className="text-sm text-gray-600 hover:text-gray-800"
          >
            Sign out
          </button>
        </div>

        <div className="mb-6">
          <PostForm />
        </div>

        <div className="space-y-4">
          <PostList posts={posts} />
        </div>
        <div className="mt-6 flex items-center justify-between">
          <button
            onClick={loadPreviousPage}
            disabled={pageNumber <= 1}
            className={`rounded bg-gray-200 px-4 py-2 ${
              pageNumber <= 1 ? 'cursor-not-allowed opacity-50' : ''
            }`}
          >
            Previous
          </button>
          <button
            onClick={loadNextPage}
            disabled={posts.length < pageSize}
            className={`rounded bg-gray-200 px-4 py-2 ${
              posts.length < pageSize ? 'cursor-not-allowed opacity-50' : ''
            }`}
          >
            Next
          </button>
        </div>
        <div className="mt-4 text-center text-xs text-gray-500">
          {numUsers} user{numUsers > 1 ? 's' : ''} online
        </div>
      </div>
    </div>
  );
}

function ProfileAvatar() {
  const user = db.useUser();
  const profile = useRequiredProfile();
  const [isUploading, setIsUploading] = useState(false);
  const avatarPath = `${user.id}/avatar`;

  const handleAvatarDelete = async () => {
    if (!profile.avatar) return;
    db.transact(db.tx.$files[lookup('path', avatarPath)].delete());
  };

  const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    setIsUploading(true);
    try {
      const { data } = await db.storage.uploadFile(avatarPath, file);
      await db.transact(db.tx.profiles[profile.id].link({ avatar: data.id }));
    } catch (error) {
      console.error('Upload failed:', error);
    }
    setIsUploading(false);
  };

  return (
    <div className="flex items-center gap-4">
      <label className="relative cursor-pointer">
        {profile.avatar ? (
          <img
            src={profile.avatar.url}
            alt={profile.handle}
            className="h-16 w-16 rounded-full border-2 border-gray-800 object-cover"
          />
        ) : (
          <div className="flex h-16 w-16 items-center justify-center rounded-full border-2 border-gray-800 bg-white text-xl font-bold text-gray-800">
            {profile.handle[0].toUpperCase()}
          </div>
        )}

        {isUploading && (
          <div className="absolute inset-0 flex items-center justify-center rounded-full bg-black bg-opacity-50">
            <div className="h-6 w-6 animate-spin rounded-full border-2 border-white border-t-transparent" />
          </div>
        )}

        <input
          type="file"
          accept="image/*"
          onChange={handleAvatarUpload}
          className="hidden"
          disabled={isUploading}
        />
      </label>
      <div className="flex flex-col">
        <div className="font-medium">handle: {profile.handle}</div>
        <div className="text-sm">email: {user.email}</div>
        <button
          onClick={handleAvatarDelete}
          className="text-left text-sm text-gray-500 hover:text-gray-700 disabled:text-gray-400"
          disabled={!profile.avatar || isUploading}
        >
          Delete Avatar
        </button>
      </div>
    </div>
  );
}

function PostForm() {
  const user = db.useUser();
  const [value, setValue] = useState('');

  const publishShout = db.rooms.usePublishTopic(room, 'shout');

  const handleSubmit = (action: string) => {
    if (!value.trim()) return;

    if (action === 'post') {
      addPost(value, user?.id);
    } else {
      const params = makeShout(value);
      addShout(params);
      publishShout(params);
    }
    setValue('');
  };

  return (
    <div className="space-y-3">
      <input
        className="w-full rounded-lg border-2 border-gray-800 px-4 py-3 focus:border-gray-600 focus:outline-none"
        autoFocus
        placeholder="What's on your mind?"
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
        onKeyDown={(e) => e.key === 'Enter' && handleSubmit('post')}
      />
      <div className="flex gap-3">
        <button
          className="rounded-full border-2 border-gray-800 bg-white px-6 py-2 font-medium hover:bg-gray-100"
          onClick={() => handleSubmit('post')}
        >
          Add to wall
        </button>
        <button
          className="rounded-full border-2 border-gray-800 bg-white px-6 py-2 font-medium hover:bg-gray-100"
          onClick={() => handleSubmit('shout')}
        >
          Shout to void
        </button>
      </div>
    </div>
  );
}

function PostList({ posts }: { posts: PostsWithProfile[] }) {
  const user = db.useUser();
  return (
    <div className="space-y-3">
      {posts.map((post) => (
        <div
          key={post.id}
          className="rounded-lg border-2 border-gray-800 bg-white p-4"
        >
          <div className="flex items-start gap-3">
            <div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full border-2 border-gray-800 bg-white font-bold text-gray-800">
              {post.author?.avatar ? (
                <img
                  className="h-full w-full rounded-full object-cover"
                  src={post.author.avatar.url}
                  alt={post.author.handle}
                />
              ) : (
                <span>{post.author?.handle[0].toUpperCase()}</span>
              )}
            </div>
            <div className="flex-1">
              <div className="flex items-start justify-between">
                <div>
                  <div className="font-medium">
                    {post.author?.handle || 'Unknown'}
                  </div>
                  <div className="text-xs text-gray-500">
                    {new Date(post.createdAt).toLocaleString()}
                  </div>
                </div>
                {post.author?.id === user?.id && (
                  <button
                    onClick={() => deletePost(post.id)}
                    className="text-sm text-gray-400 hover:text-gray-600"
                  >
                    ×
                  </button>
                )}
              </div>
              <div className="mt-2 text-gray-800">{post.text}</div>
            </div>
          </div>
        </div>
      ))}
    </div>
  );
}

// CRITICAL: Use db.SignedIn and db.SignedOut to handle authentication state
function App() {
  return (
    <div>
      <db.SignedIn>
        <EnsureProfile>
          <Main />
        </EnsureProfile>
      </db.SignedIn>
      <db.SignedOut>
        <Login />
      </db.SignedOut>
    </div>
  );
}

export default App;
